/*******************************************************************************
 * 作者:    水煮肉片饭(27185709@qq.com)
 * 版本:    v2.1
 * 描述:    动作残影
 * 给节点添加残影，残影会跟随节点移动，并跟随节点播放动画
 * 残影动画支持Animation和Spine
*******************************************************************************/
const { ccclass, property, executeInEditMode, playOnFocus, menu } = cc._decorator;
class ShadowData {
    x: number = 0;
    y: number = 0;
    angle: number = 0;
    scaleX: number = 1;
    scaleY: number = 1;
    scaleRatio: number = 1;
    aniActionName: string = '';
    aniFrameTime: number = 0;
    spActionName: string = '';
    spFrameTime: number = 0;
}
@ccclass
@executeInEditMode
@playOnFocus
@menu('Comp/ActionShadow')
export default class ActionShadow extends cc.Component {
    @property
    private _isActive: boolean = true;
    @property({ displayName: CC_DEV && '是否激活', tooltip: CC_DEV && '设置残影可见性\n激活会重置残影位置' })
    get isActive() { return this._isActive; }
    set isActive(value: boolean) {
        this._isActive = value;
        if (value) {
            if (this.node.active && this.shadowNode) {
                this.shadowNode.active = true;
                this.shadowData.length && this.updateShadowData();
            }
        } else {
            this.shadowNode.active = false;
        }
    }
    @property()
    private _shadowNum: number = 10;
    @property({ type: cc.Integer, displayName: CC_DEV && '残影数量' })
    get shadowNum(): number { return this._shadowNum; }
    set shadowNum(value: number) {
        this._shadowNum = Math.max(value, 0);
        this.updateShadowNum();
        this.updateDeltTime();
        this.updateShadowData();
        this.updateColor();
    }
    @property()
    private _deltTime: number = 4;
    @property({ type: cc.Integer, displayName: CC_DEV && '延迟帧数' })
    get deltTime(): number { return this._deltTime; }
    set deltTime(value: number) {
        this._deltTime = Math.max(value, 1);
        this.updateDeltTime();
        this.updateShadowData();
    }
    @property()
    private _shadowColor: cc.Color = cc.color(255, 255, 255);
    @property({ displayName: CC_DEV && '残影颜色' })
    get shadowColor(): cc.Color { return this._shadowColor; }
    set shadowColor(value: cc.Color) {
        this._shadowColor = value;
        this.updateColor();
    }
    @property()
    private _shadowScale: number = 0.1;
    @property({ min: 0, max: 1, step: 0.1, slide: true, displayName: CC_DEV && '尾部缩放' })
    get shadowScale(): number { return this._shadowScale; }
    set shadowScale(value: number) {
        this._shadowScale = value;
        this.updateShadowData();
    }
    @property()
    private _opacity: number = 150;
    @property({ type: cc.Integer, min: 0, max: 255, slide: true, displayName: CC_DEV && '残影透明度' })
    get opacity(): number { return this._opacity; }
    set opacity(value: number) {
        this._opacity = value;
        this.updateOpacity();
    }
    private nodeOpacity: number = 255;
    private shadowNode: cc.Node = null;
    private shadowData: ShadowData[] = [];
    private aniModel: cc.Animation = null;
    private spModel: sp.Skeleton = null;

    protected start() {
        let shadowNodeName = this.node.name + '<ActionShadow>';
        this.shadowNode = this.node.parent.getChildByName(shadowNodeName);
        if (!this.shadowNode) {
            this.shadowNode = new cc.Node(shadowNodeName);
            this.shadowNode.setParent(this.node.parent);
            this.shadowNode.setSiblingIndex(this.node.getSiblingIndex());
        }
        if (CC_EDITOR) {
            this.shadowNode['_objFlags'] = 0;
            // this.shadowNode['_objFlags'] |= cc.Object['Flags'].HideInHierarchy;  //残影节点在层级管理器中显示
            this.shadowNode['_objFlags'] |= cc.Object['Flags'].LockedInEditor;      //残影节点在层级管理器中锁定
        }
        this.nodeOpacity = this.node.opacity;
        this.aniModel = this.node.getComponent(cc.Animation);
        this.aniModel && (this.aniModel.currentClip = this.aniModel.defaultClip);
        this.spModel = this.node.getComponent(sp.Skeleton);
        this.node['_updateWorldMatrix']();
        this.updateShadowNum();
        this.updateDeltTime();
        this.updateShadowData();
        this.updateColor();
        this.updateOpacity();
    }
    //设置组件的isActive属性为true，可以重置残影位置
    protected onEnable() {
        if (this._isActive && this.shadowNode) {
            this.shadowNode.active = true;
            this.shadowData.length && this.updateShadowData();
        }
    }

    protected onDisable() {
        this.shadowNode.active = false;
    }

    protected update() {
        if (this.nodeOpacity !== this.node.opacity) {
            this.nodeOpacity = this.node.opacity;
            this.updateOpacity();
        }
        let shadowNode = this.shadowNode, shadowData = this.shadowData, deltTime = this._deltTime;
        //shadowData数组数据更新，把前一个复制给后一个
        for (let i = this._shadowNum * deltTime; i > 0; --i) {
            let cur = shadowData[i];
            let prev = shadowData[i - 1];
            cur.x = prev.x;
            cur.y = prev.y;
            cur.scaleX = prev.scaleX;
            cur.scaleY = prev.scaleY;
            cur.angle = prev.angle;
            if (this.aniModel !== null) {
                cur.aniActionName = prev.aniActionName;
                cur.aniFrameTime = prev.aniFrameTime;
            }
            if (this.spModel !== null) {
                cur.spActionName = prev.spActionName;
                cur.spFrameTime = prev.spFrameTime;
            }
        }
        //把本体的属性添加到shadowData数组里
        let data = shadowData[0];
        let m = this.node['_worldMatrix'].m;
        data.x = m[12];
        data.y = m[13];
        data.scaleX = this.node.scaleX;
        data.scaleY = this.node.scaleY;
        data.angle = this.node.angle;
        if (this.aniModel !== null) {
            data.aniActionName = this.aniModel.currentClip.name;
            data.aniFrameTime = this.aniModel.getAnimationState(data.aniActionName).time;
        }
        if (this.spModel !== null) {
            data.spActionName = this.spModel.animation ?? this.spModel.defaultAnimation;
            data.spFrameTime = this.spModel.getState()?.tracks[0]?.getAnimationTime() ?? 0;
        }
        //用shadowData里的数据设置所有残影的属性
        shadowNode['_updateWorldMatrix']();
        m = shadowNode['_worldMatrix'].m;
        let m12 = m[12], m13 = m[13];
        for (let i = this._shadowNum - 1; i > -1; --i) {
            let node = shadowNode.children[i];
            data = shadowData[deltTime * (i + 1)];
            node.setPosition(data.x - m12, data.y - m13);
            node.setScale(data.scaleX * data.scaleRatio, data.scaleY * data.scaleRatio);
            node.angle = data.angle;
            //设置Animation动画和帧数
            let aniModel = node.getComponent(cc.Animation);
            if (aniModel !== null) {
                let state = aniModel.getAnimationState(data.aniActionName);
                aniModel.currentClip = state.clip;
                state['setTime'](data.aniFrameTime);
                CC_EDITOR && state.play();
            }
            //设置Spine动画和帧数
            let spine = node.getComponent(sp.Skeleton);
            if (spine !== null) {
                let state = spine.getState();
                if (!state) break;
                let animation = state.data.skeletonData['findAnimation'](data.spActionName);
                if (animation) {
                    state.trackEntry && state.setCurrent(0, state.trackEntry(0, animation, false), true);
                    state.tracks[0].trackTime = data.spFrameTime;
                } else {
                    spine.clearTrack(0);
                    spine.setToSetupPose();
                }
            }
        }
    }
    //当残影数量改变时，更新残影节点
    private updateShadowNum() {
        this.shadowNode.removeAllChildren();
        this.shadowNode.destroyAllChildren();
        for (let i = 0, len = this._shadowNum; i < len; ++i) {
            let node = cc.instantiate(this.node);
            node.name = this.node.name + i;
            let cmps = node['_components'];
            for (let j = cmps.length - 1; j > -1; --j) {
                if (cmps[j] instanceof cc.Animation) continue;
                if (cmps[j] instanceof cc.RenderComponent) continue;
                cmps[j].destroy();
            }
            node['_objFlags'] |= cc.Object['Flags'].LockedInEditor;
            node.setParent(this.shadowNode);
        }
    }

    private updateDeltTime() {
        this.shadowData = [];
        for (let i = this._shadowNum * this._deltTime, shadowData = this.shadowData; i > -1; shadowData[i--] = new ShadowData());
    }

    private updateShadowData() {
        let scaleDelt = (1 - this._shadowScale) / (this._shadowNum * this._deltTime);
        let m = this.node['_worldMatrix'].m, m12 = m[12], m13 = m[13];
        for (let i = this._shadowNum * this._deltTime; i > -1; --i) {
            let data = this.shadowData[i];
            data.x = m12;
            data.y = m13;
            data.scaleX = this.node.scaleX;
            data.scaleY = this.node.scaleY;
            data.scaleRatio = 1 - i * scaleDelt;
            data.angle = this.node.angle;
            if (this.aniModel !== null) {
                data.aniActionName = this.aniModel.currentClip.name;
                data.aniFrameTime = this.aniModel.getAnimationState(data.aniActionName).time;
                CC_EDITOR && this.aniModel.play(data.aniActionName, data.aniFrameTime);
            }
            if (this.spModel !== null) {
                data.spActionName = this.spModel.animation;
                data.spFrameTime = this.spModel.getState()?.tracks[0]?.getAnimationTime() ?? 0;
            }
        }
    }

    private updateColor() {
        let setColor = (node: cc.Node, color: cc.Color) => {
            node.color = color;
            for (let i = node.childrenCount - 1; i > -1; setColor(node.children[i--], color));
        }
        setColor(this.shadowNode, this._shadowColor);
    }

    private updateOpacity() {
        this.shadowNode.opacity = this._opacity * this.node.opacity / 255;
    }

    protected onDestroy() {
        if (cc.isValid(this.shadowNode)) {
            this.shadowNode.removeFromParent();
            this.shadowNode.destroy();
        };
    }
}